package lt.mockos.app.client.cpu.hw;

import lt.mockos.app.client.cpu.hw.type.Registers;


public class Memory {

    String[] memory;
    public static final int WORDS_PER_BLOCK = 10;
    public static final int BLOCKS_PER_VM = 11;
    public static final int NUMBER_OF_VM = 3;
    public static final int MEMORY_SIZE = WORDS_PER_BLOCK * BLOCKS_PER_VM * NUMBER_OF_VM;
    public static final int BLOCKS_PER_DS = 3;
    public static final int BLOCKS_PER_CS = 4;
    public static final int BLOCKS_PER_SS = 3;

    public Memory() {
        memory = new String[MEMORY_SIZE];
        for (int i = 0; i < MEMORY_SIZE; i++)
            memory[i] = "";
    }

    public String[] getMemory() {
        return memory;
    }

    private Registers registers;

    public void allocateVMMemory(Registers registers) {
        this.registers = registers;
        // find free block to save pages table there
        
        int ptr = -1;        
        for (int i = 0; i < MEMORY_SIZE; i += WORDS_PER_BLOCK)
            if (memory[i].compareTo("") == 0) {
                memory[i] = "+";
                ptr = i;
                break;
            }
        
        if (-1 == ptr){
            registers.PI = 1;
            return;
        }
        
        for (int i = ptr; i < ptr + WORDS_PER_BLOCK; i++)
            for (int j = ptr; j < MEMORY_SIZE; j += WORDS_PER_BLOCK) {
                if (memory[j].compareTo("") == 0) {
                    memory[j] = "+";
                    memory[i] = String.valueOf(j);
                    break;
                }
            }

        registers.PTR = ptr;
        registers.DS = 0;
        registers.CS = BLOCKS_PER_DS * WORDS_PER_BLOCK;
        registers.SS = (BLOCKS_PER_DS + BLOCKS_PER_CS) * WORDS_PER_BLOCK;
        registers.SP = 0;
        registers.CP = 0;
    }

    public void deallocateVMMemory(int ptr) {
        for (int i = ptr; i < ptr + WORDS_PER_BLOCK; i++) {
            for (int j = Integer.parseInt(memory[i]); j < Integer.parseInt(memory[i]) + WORDS_PER_BLOCK; j++) {
                memory[j] = "";
            }
            memory[i] = "";
        }

//        registers.clean();
    }

    public boolean loadTask(int ptr, String source) {
        String dataSegmentSignature = "DATA_SEGMENT\n";
        String codeSegmentSignature = "CODE_SEGMENT\n";

        int posStart = source.indexOf(dataSegmentSignature);
        if (-1 == posStart)
            return false;
        posStart += dataSegmentSignature.length();
        int posEnd = source.indexOf(codeSegmentSignature);
        if (-1 == posEnd)
            return false;

        String data = source.substring(posStart, posEnd);
        posStart = posEnd + codeSegmentSignature.length();
        String code = source.substring(posStart);

        loadDataSegment(ptr, data);
        loadCodeSegment(ptr, code);

        return true;

    }

    private void loadDataSegment(int ptr, String source) {
        String[] definitions = source.split("\n");
        for (int i = 0; i < definitions.length; i++) {
            String def = definitions[i];
            String[] parts = def.split(" ");
            String value = parts[1];

            writeWord(ptr, i, Memory.Segment.Segment_Data, value);
        }

    }

    private void loadCodeSegment(int ptr, String source) {
        String delims = "\n";
        String[] commands = source.split(delims);
        for (int i = 0; i < commands.length; i++) {
            String cmd = commands[i];
            if (cmd.compareTo("PRINTS") == 0)
                cmd = "PRNS";
            else if (cmd.compareTo("PRINTW") == 0)
                cmd = "PRNW";
            else if (cmd.compareTo("SCANW") == 0)
                cmd = "SCNW";

            if (cmd.length() == 3)
                cmd.concat(" ");

            writeWord(ptr, i, Memory.Segment.Segment_Code, cmd);
        }
    }

    public void writeWord(int ptr, int address, Segment segment, String word) {
        int x = address / WORDS_PER_BLOCK;
        int y = address % WORDS_PER_BLOCK;
        int start = Integer.parseInt(memory[ptr + x]); // Data segment
        if (Segment.Segment_Code == segment) {
            start = Integer.parseInt(memory[ptr + BLOCKS_PER_DS + x]); // Code segment
        } else if (Segment.Segment_Stack == segment) {
            start = Integer.parseInt(memory[ptr + BLOCKS_PER_DS + BLOCKS_PER_CS + x]); // Stack segment
        }

        memory[start + y] = word;
    }

    public enum Segment {
        Segment_Data, Segment_Code, Segment_Stack
    }

    public String readWord(int ptr, int address, Segment segment) {
        int x = address / WORDS_PER_BLOCK;
        int y = address % WORDS_PER_BLOCK;
        int start = Integer.parseInt(memory[ptr + x]); // Data segment
        if (Segment.Segment_Code == segment) {
            start = Integer.parseInt(memory[ptr + BLOCKS_PER_DS + x]); // Code segment
        } else if (Segment.Segment_Stack == segment) {
            start = Integer.parseInt(memory[ptr + BLOCKS_PER_DS + BLOCKS_PER_CS + x]); // Stack segment
        }

        return memory[start + y];
    }

    public int getAddress(int x, int y) {
        return x * WORDS_PER_BLOCK + y;
    }

    public boolean isValidAddress(int ptr, int x, int y, Segment segment) {
        int address = getAddress(x, y);
        int dsStart = Converter.stringToInt(memory[ptr]);
        int csStart = Converter.stringToInt(memory[ptr + BLOCKS_PER_DS]);
        int ssStart = Converter.stringToInt(memory[ptr + BLOCKS_PER_DS + BLOCKS_PER_CS]);
        int ssEnd = Converter.stringToInt(memory[ptr + BLOCKS_PER_DS + BLOCKS_PER_CS + BLOCKS_PER_SS]);
        if (Segment.Segment_Data == segment) {
            if (address + dsStart >= csStart)
                return false;
        } else if (Segment.Segment_Code == segment) {
            if (address + csStart >= ssStart)
                return false;
        } else if (Segment.Segment_Stack == segment) {
            if (address + ssStart >= ssEnd)
                return false;
        }

        return true;
    }

}
